Writing templates¶
Writing templates is simple.
To create template, take data that needs to be parsed and replace portions of it with match variables:
# Data we want to parse
interface Loopback0
description Router-id-loopback
ip address 192.168.0.113/24
!
interface Vlan778
description CPE_Acces_Vlan
ip address 2002::fd37/124
ip vrf CPE1
!
# TTP template
interface {{ interface }}
ip address {{ ip }}/{{ mask }}
description {{ description }}
ip vrf {{ vrf }}
Above data and template can be saved in two files, and ttp CLI tool can be used to parse it with command:
ttp -d "/path/to/data/file.txt" -t "/path/to/template.txt" --outputter json
And get these results:
[
[
{
"description": "Router-id-loopback",
"interface": "Loopback0",
"ip": "192.168.0.113",
"mask": "24"
},
{
"description": "CPE_Acces_Vlan",
"interface": "Vlan778",
"ip": "2002::fd37",
"mask": "124",
"vrf": "CPE1"
}
]
]
Above process is very similar to writing Jinja2 templates but in reverse direction - we have text and we need to transform it into structured data, as opposed to having structured data, that needs to be rendered with Jinja2 template to produce text.
Warning
Indentation is important. Trailing spaces and tabs are ignored by TTP.
TTP use leading spaces and tabs to produce better match results, exact number of leading spaces and tabs used to form regular expressions. There is a way to ignore indentation by the use of ignore indicator coupled with [\s\t]*
or \s+
or \s{1,3}
or \t+
etc. regular expressions.
TTP supports various output formats, for instance, if we need to emit data not in json but csv format we can use outputter and write this template:
<group>
interface {{ interface }}
ip address {{ ip }}/{{ mask }}
description {{ description }}
ip vrf {{ vrf }}
</group>
<output format="csv" returner="terminal"/>
Note
run ttp CLI tool without -o option to print only results produced by outputter defined within template - ttp -d “/path/to/data/file.txt” -t “/path/to/template.txt”
We told TTP that returner is terminal
, because of that results will be printed to terminal screen:
description,interface,ip,mask,vrf
Router-id-loopback,Loopback0,192.168.0.113,24,
CPE_Acces_Vlan,Vlan778,2002::fd37,124,CPE1
Parsing hierarchical configuration data¶
TTP can use simple templates that does not contain much hierarchy (same as the data that parsed by them), but what to do if we want to extract information from below text:
router bgp 12.34
address-family ipv4 unicast
router-id 1.1.1.1
!
vrf CT2S2
rd 102:103
!
neighbor 10.1.102.102
remote-as 102.103
address-family ipv4 unicast
send-community-ebgp
route-policy vCE102-link1.102 in
route-policy vCE102-link1.102 out
!
!
neighbor 10.2.102.102
remote-as 102.103
address-family ipv4 unicast
route-policy vCE102-link2.102 in
route-policy vCE102-link2.102 out
!
!
vrf AS65000
rd 102:104
!
neighbor 10.1.37.7
remote-as 65000
address-family ipv4 labeled-unicast
route-policy PASS-ALL in
route-policy PASS-ALL out
In such a case we have to use ttp groups to define nested, hierarchical structure, sample template might look like this:
<group name="bgp_cfg">
router bgp {{ ASN }}
<group name="ipv4_afi">
address-family ipv4 unicast {{ _start_ }}
router-id {{ bgp_rid }}
</group>
<group name="vrfs">
vrf {{ vrf }}
rd {{ rd }}
<group name="neighbors">
neighbor {{ neighbor }}
remote-as {{ neighbor_asn }}
<group name="ipv4_afi">
address-family ipv4 unicast {{ _start_ }}
send-community-ebgp {{ send_community_ebgp | set("Enabled") }}
route-policy {{ RPL_IN }} in
route-policy {{ RPL_OUT }} out
</group>
</group>
</group>
</group>
Above data and template can be saved in two files and run using ttp CLI tool with command:
ttp -d "/path/to/data/file.txt" -t "/path/to/template.txt" --outputter yaml
These results will be printed to screen:
- bgp_cfg:
ASN: '12.34'
ipv4_afi:
bgp_rid: 1.1.1.1
vrfs:
- neighbors:
- ipv4_afi:
RPL_IN: vCE102-link1.102
RPL_OUT: vCE102-link1.102
send_community_ebgp: Enabled
neighbor: 10.1.102.102
neighbor_asn: '102.103'
- ipv4_afi:
RPL_IN: vCE102-link2.102
RPL_OUT: vCE102-link2.102
neighbor: 10.2.102.102
neighbor_asn: '102.103'
rd: 102:103
vrf: CT2S2
- neighbors:
- ipv4_afi:
RPL_IN: PASS-ALL
RPL_OUT: PASS-ALL
- neighbor: 10.1.37.7
neighbor_asn: '65000'
rd: 102:104
vrf: AS65000
Not too bad, but let’s say we want VRFs to be represented as a dictionary with VRF names as keys, same goes for neighbors - we want them to be a dictionary with neighbor IPs as a key, we can use TTP dynamic path feature together with path formatters to accomplish exactly that, here is the template:
<group name="bgp_cfg">
router bgp {{ ASN }}
<group name="ipv4_afi">
address-family ipv4 unicast {{ _start_ }}
router-id {{ bgp_rid }}
</group>
!
<group name="vrfs.{{ vrf }}">
vrf {{ vrf }}
rd {{ rd }}
!
<group name="peers.{{ neighbor }}**">
neighbor {{ neighbor }}
remote-as {{ neighbor_asn }}
<group name="ipv4_afi">
address-family ipv4 unicast {{ _start_ }}
send-community-ebgp {{ send_community_ebgp | set("Enabled") }}
route-policy {{ RPL_IN }} in
route-policy {{ RPL_OUT }} out
</group>
</group>
</group>
</group>
After parsing TTP will print these structure:
- bgp_cfg:
ASN: '12.34'
ipv4_afi:
bgp_rid: 1.1.1.1
vrfs:
AS65000:
peers:
10.1.37.7:
ipv4_afi:
RPL_IN: PASS-ALL
RPL_OUT: PASS-ALL
neighbor_asn: '65000'
rd: 102:104
CT2S2:
peers:
10.1.102.102:
ipv4_afi:
RPL_IN: vCE102-link1.102
RPL_OUT: vCE102-link1.102
send_community_ebgp: Enabled
neighbor_asn: '102.103'
10.2.102.102:
ipv4_afi:
RPL_IN: vCE102-link2.102
RPL_OUT: vCE102-link2.102
neighbor_asn: '102.103'
rd: 102:103
That’s better, but what actually changed to have such a different results, well, not to much by the look of it, but quite a lot in fact.
TTP group’s name attribute actually used as a path where to save group parsing results within results tree, to denote different levels dot symbol can be used, that is how we get new vrf and peers keys in the output.
In addition we used TTP dynamic path feature by introducing {{ vrf }}
and {{ neighbor }}
in the name of the group, that will be dynamically substituted with matching results.
Moreover, we also have to use double star **
path formatter to tell TTP that {{ neighbor }}
child content should be kept as a dictionary and not transformed into list (default behavior) whenever we add new data to that portion of results tree.
Parse text tables¶
TBD
Parse show commands output¶
TBD
Filtering with TTP¶
TBD
Outputting results¶
TBD